﻿using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Linq;

namespace Devonline.Database.MongoDB;

/// <summary>
/// MongoDB 数据服务得默认实现
/// </summary>
public class MongoService(ILogger<MongoService> logger, IMongoEndpoint endpoint) : NoSQLService(logger, endpoint), IMongoService, INoSQLService
{
    protected readonly IMongoEndpoint _mongoEndpoint = endpoint;
    protected readonly IMongoDatabase _database = new MongoClient(endpoint.ConnectionString).GetDatabase(endpoint.Database);

    /// <summary>
    /// 根据过滤条件返回查询结果list
    /// </summary>
    /// <typeparam name="TModel">数据对象模型的类型</typeparam>
    /// <returns></returns>
    public override async Task<IQueryable<TModel>> GetQueryableAsync<TModel>()
    {
        var typeName = typeof(TModel).GetDisplayName();
        _logger.LogDebug("用户将查询 {host} 的数据库 {database}", _endpoint.Host, _endpoint.Database);
        var collection = await GetOrCreateCollectionAsync<TModel>();
        var queryable = collection.AsQueryable();

        _logger.LogDebug($"The user query {typeName} from cache");
        if (DataIsolate != DataIsolateLevel.None && DataIsolateId is not null && queryable is IQueryable<IIsolate>)
        {
            _logger.LogDebug($"The user query {typeName} from cache in data isolate: {DataIsolateId}");
            queryable = queryable.Where(x => ((IIsolate)x).Isolate.Equals(DataIsolateId));
        }

        return queryable;
    }

    /// <summary>
    /// 在 mongo 模式下, 默认的全字段更新方法不使用
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="model"></param>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    public override Task UpdateAsync<TModel>(TModel model) => throw new NotImplementedException();
    /// <summary>
    /// 在 mongo 模式下, 默认的全字段更新方法不使用
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="data"></param>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    public override Task UpdatesAsync<TModel>(IEnumerable<TModel> data) => throw new NotImplementedException();

    /// <summary>
    /// Mongo 的修改, 需要指定列名, 只能对部分列内容进行修改, 否则性能不高
    /// </summary>
    /// <typeparam name="TModel">当前对象的模型类型</typeparam>
    /// <param name="model">当前对象</param>
    /// <param name="fields">所有需要修改的字段名</param>
    /// <returns></returns>
    public virtual async Task UpdateAsync<TModel>(TModel model, params string[] fields) where TModel : class, IEntitySet, new()
    {
        var propertyInfos = typeof(TModel).GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.IgnoreCase).Where(x => x.CanWrite && fields.Contains(x.Name));
        if (propertyInfos.Any())
        {
            object? propertyValue = null;
            var update = Builders<TModel>.Update;
            UpdateDefinition<TModel>? updateDefinition = null;
            foreach (var propertyInfo in propertyInfos)
            {
                propertyValue = propertyInfo.GetValue(model);
                updateDefinition = (updateDefinition is null) ? update.Set(propertyInfo.Name, propertyValue) : updateDefinition.Set(propertyInfo.Name, propertyValue);
            }

            await (await GetOrCreateCollectionAsync<TModel>()).UpdateOneAsync(x => x.Id == model.Id, updateDefinition);
        }
    }
    /// <summary>
    /// Mongo 的修改, 需要指定列名, 只能对部分列内容进行修改, 否则性能不高
    /// </summary>
    /// <typeparam name="TModel">当前对象的模型类型</typeparam>
    /// <param name="data">修改的对象列表</param>
    /// <param name="fields">所有需要修改的字段名</param>
    /// <returns></returns>
    public virtual async Task UpdatesAsync<TModel>(IEnumerable<TModel> data, params string[] fields) where TModel : class, IEntitySet, new()
    {
        var collection = await GetOrCreateCollectionAsync<TModel>();
        var propertyInfos = typeof(TModel).GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.IgnoreCase).Where(x => x.CanWrite && fields.Contains(x.Name));
        if (propertyInfos.Any())
        {
            object? propertyValue = null;
            var update = Builders<TModel>.Update;
            UpdateDefinition<TModel>? updateDefinition = null;
            foreach (var item in data)
            {
                updateDefinition = null;
                foreach (var propertyInfo in propertyInfos)
                {
                    propertyValue = propertyInfo.GetValue(item);
                    updateDefinition = (updateDefinition is null) ? update.Set(propertyInfo.Name, propertyValue) : updateDefinition.Set(propertyInfo.Name, propertyValue);
                }

                await collection.UpdateOneAsync(x => x.Id == item.Id, updateDefinition);
            }
        }
    }
    /// <summary>
    /// 根据表达式删除指定数据
    /// </summary>
    /// <typeparam name="TModel">当前数据对象模型的类型</typeparam>
    /// <param name="predicate">需要删除的表达式</param>
    /// <returns></returns>
    public virtual async Task DeleteAsync<TModel>(Expression<Func<TModel, bool>> predicate) where TModel : class, IEntitySet, new()
    {
        await (await GetOrCreateCollectionAsync<TModel>()).DeleteManyAsync(predicate);
    }
    /// <summary>
    /// 根据 id 删除
    /// </summary>
    /// <typeparam name="TModel">当前数据对象模型的类型</typeparam>
    /// <param name="id">要删除的数据编号</param>
    /// <param name="context">数据服务上下文</param>
    /// <returns></returns>
    public override async Task DeleteAsync<TModel>(string id, DataServiceContext? context = null)
    {
        await (await GetOrCreateCollectionAsync<TModel>()).DeleteOneAsync(m => m.Id.Equals(id));
    }
    /// <summary>
    /// 根据 id 删除
    /// </summary>
    /// <typeparam name="TModel">当前数据对象模型的类型</typeparam>
    /// <param name="ids">要删除的数据编号</param>
    /// <param name="context">数据服务上下文</param>
    /// <returns></returns>
    public override async Task DeletesAsync<TModel>(IEnumerable<string> ids, DataServiceContext? context = null)
    {
        if (ids.Any())
        {
            await (await GetOrCreateCollectionAsync<TModel>()).DeleteManyAsync(m => ids.Contains(m.Id));
        }
    }

    /// <summary>
    /// 新增业务数据对象中 TElement 类型的集合对象
    /// </summary>
    /// <param name="field">业务数据</param>
    /// <param name="elements">引用数据集合</param>
    /// <returns></returns>
    public virtual async Task AddCollectionAsync<TModel, TElement>(TModel model, string field, IEnumerable<TElement> elements) where TModel : class, IEntitySet, new() where TElement : class, IEntitySet => await (await GetOrCreateCollectionAsync<TModel>()).UpdateOneAsync(x => x.Id == model.Id, Builders<TModel>.Update.PushEach(field, elements));
    /// <summary>
    /// 删除业务数据对象中 TElement 类型的集合对象
    /// </summary>
    /// <param name="field">业务数据</param>
    /// <param name="elements">删除数据集合</param>
    /// <returns></returns>
    public virtual async Task DeleteCollectionAsync<TModel, TElement>(TModel model, string field, IEnumerable<TElement> elements) where TModel : class, IEntitySet, new() where TElement : class, IEntitySet => await (await GetOrCreateCollectionAsync<TModel>()).UpdateOneAsync(x => x.Id == model.Id, Builders<TModel>.Update.PullAll(field, elements));

    /// <summary>
    /// 获取或创建 MongoDB 的数据集合
    /// </summary>
    /// <typeparam name="TModel">数据对象模型的类型</typeparam>
    /// <returns></returns>
    protected virtual async Task<IMongoCollection<TModel>> GetOrCreateCollectionAsync<TModel>()
    {
        var tableName = typeof(TModel).GetTableName() ?? _endpoint.DataTable;
        var collection = _database.GetCollection<TModel>(tableName);
        if (collection is null)
        {
            await _database.CreateCollectionAsync(tableName);
            collection = _database.GetCollection<TModel>(tableName);
        }

        return collection;
    }
    /// <summary>
    /// 调用实例的 Create 方法为基础字段赋值
    /// </summary>
    /// <typeparam name="TModel">业务数据类型</typeparam>
    /// <param name="model">业务数据</param>
    public virtual void Create<TModel>(TModel model)
    {
        if (model is IEntitySetWithCreate mongoModelWithCreate)
        {
            mongoModelWithCreate.Create();
        }
    }
    /// <summary>
    /// 调用实例的 Update 方法为基础字段赋值
    /// </summary>
    /// <typeparam name="TModel">业务数据类型</typeparam>
    /// <param name="model">业务数据</param>
    public virtual void Update<TModel>(TModel model)
    {
        if (model is IEntitySetWithCreateAndUpdate modelWithCreateAndUpdate)
        {
            modelWithCreateAndUpdate.Update();
        }
    }

    /// <summary>
    /// 写入一行数据到数据库
    /// </summary>
    /// <typeparam name="TModel">数据对象模型的类型</typeparam>
    /// <param name="model">待写入的数据</param>
    /// <returns></returns>
    protected override async Task InternalAddAsync<TModel>(TModel model)
    {
        Create(model);
        Update(model);
        await (await GetOrCreateCollectionAsync<TModel>()).InsertOneAsync(model);
    }
    /// <summary>
    /// 写入多行数据到数据库
    /// </summary>
    /// <typeparam name="TModel">数据对象模型的类型</typeparam>
    /// <param name="data">待写入的数据</param>
    /// <returns></returns>
    protected override async Task InternalAddsAsync<TModel>(IEnumerable<TModel> data)
    {
        var adds = new List<TModel>();
        foreach (var model in data)
        {
            Create(model);
            Update(model);
            adds.Add(model);
        }

        await (await GetOrCreateCollectionAsync<TModel>()).InsertManyAsync(adds);
    }
    /// <summary>
    /// 更新数据到数据库
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="model"></param>
    /// <returns></returns>
    protected override async Task InternalUpdateAsync<TModel>(TModel model) => await (await GetOrCreateCollectionAsync<TModel>()).UpdateOneAsync(x => x.Id == model.Id, BsonDocument.Create(model));
    /// <summary>
    /// 从数据库删除数据
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="data"></param>
    /// <returns></returns>
    protected override async Task InternalUpdatesAsync<TModel>(IEnumerable<TModel> data) => await (await GetOrCreateCollectionAsync<TModel>()).UpdateManyAsync(x => data.Select(a => a.Id).Contains(x.Id), BsonDocument.Create(data));
}